from abc import abstractmethod, abstractproperty
import enum
from pathlib import Path
from typing import Callable, Generic, Optional, TypeVar
import xml.etree.ElementTree as ET
from ftraffic import TrafficInst
from feasytools import RangeList

class PluginStatus(enum.IntEnum):
    '''插件执行状态'''
    EXECUTE = 0     #当前调用必须执行插件
    HOLD = 1        #当前调用应当保留上次插件执行结果
    OFFLINE = 2     #当前调用应当返回插件离线时的返回值

PIResult = TypeVar('PIResult',covariant=True)
PIExec = Callable[[int,PluginStatus],tuple[bool,PIResult]]
PINoRet = Callable[[],None]

class PluginBase(Generic[PIResult]):
    __PreSimulation: Optional[PINoRet]
    __PreStep: Optional[PIExec]
    __PostSimulation: Optional[PINoRet]
    __PostStep: Optional[PIExec]
    def SetPreSimulation(self,func:PINoRet)->None:
        '''仿真开始前的插件预处理, 在其他参数加载完毕后运行'''
        self.__PreSimulation = func
    def SetPreStep(self,func:PIExec)->None:
        '''仿真步进前的插件工作'''
        self.__PreStep = func
    def SetPostStep(self,func:PIExec)->None:
        '''仿真步进后的插件工作'''
        self.__PostStep = func
    def SetPostSimulation(self,func:PINoRet)->None:
        '''仿真结束后的插件后处理'''
        self.__PostSimulation = func

    def __init__(self,inst:TrafficInst,elem:ET.Element,work_dir:Path,enable_time:Optional[RangeList]=None,
            interval:int=0,plugin_dependency:'list[PluginBase]'=[]):
        '''
        插件初始化
            inst: 路网仿真实例
            elem: 插件配置XML元素
            enable_time: 插件启用时间, 如果不指定则检查xml中的online子节点, 如果online子节点不存在则表示一直启用
            interval: 插件运行间隔, 单位s, 如果不指定则必须在xml中指定invterval属性
            plugin_dependency: 插件依赖列表
        '''
        self.__PreStep = None
        self.__PostStep = None
        self.__PreSimulation = None
        self.__PostSimulation = None
        self.__lastTpre = self.__lastTpost = -1
        self.__lastOkpre = False
        self.__lastOkpost = False
        self.__name = elem.tag
        self.__interval = interval if interval > 0 else int(elem.attrib.pop("interval",0))
        if self.__interval <= 0: raise ValueError("未指定或非法的插件运行间隔")
        self.__on = enable_time
        if self.__on is None:
            online_elem = elem.find("online")
            if online_elem is None: self.__on = None
            else: self.__on = RangeList(online_elem)
        self.__respre = self.__respost = self.Initialization(elem,inst,work_dir,plugin_dependency)
    
    @abstractproperty
    def Description(self)->str:
        '''获取插件描述'''
        raise NotImplementedError
    
    @property
    def Name(self)->str:
        '''获取插件名称'''
        return self.__name
    
    @property
    def Interval(self)->int:
        '''获取插件运行间隔, 单位s'''
        return self.__interval
    
    @property
    def OnlineTime(self)->Optional[RangeList]:
        '''获取插件启用时间'''
        return self.__on
    
    @property
    def LastTime(self)->int:
        '''获取上次插件处于PluginStatus.EXECUTE状态的时间'''
        return self.__lastTpre
    
    @property
    def LastPreStepSucceed(self)->bool:
        '''获取上次插件处于PluginStatus.EXECUTE状态时, PreStep是否成功'''
        return self.__lastOkpre
    
    @property
    def LastPostStepSucceed(self)->bool:
        '''获取上次插件处于PluginStatus.EXECUTE状态时, PostStep是否成功'''
        return self.__lastOkpost

    @property
    def LastPreStepResult(self)->PIResult:
        '''获取上次插件处于PluginStatus.EXECUTE状态时, PreStep的结果'''
        return self.__respre
    
    @property
    def LastPostStepResult(self)->PIResult:
        '''获取上次插件处于PluginStatus.EXECUTE状态时, PostStep的结果'''
        return self.__respost
    
    @abstractmethod
    def Initialization(self,elem:ET.Element,inst:TrafficInst,work_dir:Path,plugin_dependency:'list[PluginBase]') -> PIResult:
        '''从XML元素, TrafficInst, 工作路径和插件依赖初始化插件: 返回不在线的运行结果'''
    
    def IsOnline(self,t:int):
        '''判断插件是否在线'''
        return self.__on is None or t in self.__on
    
    def _precall(self,_t:int)->None:
        '''运行插件PreStep'''
        if self.__PreStep is None: return
        if self.__on != None and _t not in self.__on:
            self.__PreStep(_t,PluginStatus.OFFLINE)
        elif self.__lastTpre + self.__interval <= _t or self.__lastTpre < 0:
            self.__lastOkpre, self.__respre = self.__PreStep(_t,PluginStatus.EXECUTE)
            self.__lastTpre = _t
        else:
            self.__PreStep(_t,PluginStatus.HOLD)
    
    def _postcall(self,_t:int,/)->None:
        '''运行插件PostStep'''
        if self.__PostStep is None: return
        if self.__on != None and _t not in self.__on:
            self.__PostStep(_t,PluginStatus.OFFLINE)
        elif self.__lastTpost + self.__interval <= _t or self.__lastTpost < 0:
            self.__lastOkpost, self.__respost = self.__PostStep(_t,PluginStatus.EXECUTE)
            self.__lastTpost = _t
        else:
            self.__PostStep(_t,PluginStatus.HOLD)